今天要做的是...
做一個前端網頁,支援拖動圖片上傳,
把圖片轉成 base64 送給伺服器,伺服器將 base64 轉回圖片後進行辨識傳回結果。
這邊是 index.html,新增了一個 250x250 的 mycanvas,並改寫拖動事件,
mycanvas 在drop事件後會呼叫 fileReader 讀取檔案,
fileReader 讀取檔案後會寫入 img 變數,
image 物件被寫入後,會將內容畫在 mycanvas 上。
fileReader.result 和 image.src 都是字串,而格式為 data:image/png;base64, 後接 base64 字串,如下範例。
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA....
mybutton 被按下後,會將上段字串送至後端的 /mnist 路徑。
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<canvas id="mycanvas" width="250" height="250">
Your browser does not support the HTML5 canvas tag.</canvas>
<button id="mybutton">送出</button>
</body>
</html>
<script>
var mycanvas = document.getElementById("mycanvas");
var image = new Image();
var ctx = document.getElementById("mycanvas").getContext("2d");
var fileReader = new FileReader();
mycanvas.ondragover = function (e) {
e.preventDefault();
}
mycanvas.ondrop = function (e) {
e.preventDefault();
let f = e.dataTransfer.files[0];
fileReader.readAsDataURL(f);
}
fileReader.onload = function () {
image.src = fileReader.result;
}
image.onload = function () {
ctx.drawImage(image, 0, 0, 250, 250);
};
var mybutton = document.getElementById("mybutton");
mybutton.onclick = function() {
$.post("/mnist",
{
"base64_str": image.src
},
function(data, status){
alert("Data: " + data + "\nStatus: " + status);
console.log(data);
});
}
</script>
後端這裡則是新增了一個路徑 /mnist,作為辨識網址,它會接收 base64_str 變數並依照","裁切。 (因為辨識只需要 base64 字串)
成功後 flask jsonify 會將物件以 json 格式回傳。
# a01_flask_server.py
import base64
from flask import Flask, render_template, request, jsonify
app = Flask("mnist")
import a06_mnist_api
@app.route("/")
def hello_world():
return render_template('index.html')
@app.route("/mnist", methods = ['POST'])
def mnist():
# base64tag = "data:image/png;base64"
data = request.form.get("base64_str").split(",", 1)
if len(data) == 2:
return jsonify(a06_mnist_api.predict_from_base64(data[1]))
return jsonify([False])
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5050)
這邊則是進行影像處理的部分,先將 base64_str 轉成 bytes-like object,
後使用 np.frombuffer 把 bytes-like object 轉成 numpy array,
使用 cv2.imdecode 灰階模式讀取 numpy array,
然後縮放圖片至 28x28,最後丟進模型預測。
flask jsonify 不支援將 numpy 的數值直接轉為 json,所以在最後用兩層迴圈將預測結果轉成 float,並四捨五入至小數點二位。
# a06_mnist_api.py
import tensorflow as tf
import numpy as np
import base64
import cv2
saved_model_path = "mnist"
model = tf.keras.models.load_model(saved_model_path)
def predict_from_base64(base64_str):
decoded = base64.b64decode(base64_str)
np_arr = np.frombuffer(decoded,np.uint8)
imggray = cv2.imdecode(np_arr, cv2.IMREAD_GRAYSCALE)
resized = cv2.resize(imggray, (28, 28))
return predict_from_img_array(resized)
def predict_from_img_array(img_array):
input_arr = np.array([img_array]) # Convert single image to a batch.
predictions = model.predict(input_arr)
return [ [round(float(j), 2) for j in i] for i in predictions]
最後結果